package dosgi.distribution.invoker.impl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;

import dosgi.distribution.invoker.RemoteInvoker;


/**
 * Implementation of remote invoker which search for services tagged as remote and invoke methods with given arguments.
 * 
 * @author Jaroslaw Janik
 */
public class RemoteInvokerImpl implements RemoteInvoker {

	@Override
	public Object invoke(String className, String methodName, Map<Class<?>, Object> args, Map<String, String> properties) {
		BundleContext context = FrameworkUtil.getBundle(this.getClass()).getBundleContext();

		try {
			ServiceReference<?>[] references = context.getServiceReferences(className, getFilter(properties));
			
			if (references != null) {
				Object remoteObject = context.getService(references[0]);
				
				Method methodToInvoke = remoteObject.getClass().getMethod(methodName, getParametersClasses(args));
				return methodToInvoke.invoke(remoteObject, getParametersValues(args));
			} else {
				// TODO throw exception
				System.err.println("nic nie mam w sobie");
				return null;
			}
		} catch (InvalidSyntaxException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		
		return null;
	}
	
	private Class<?>[] getParametersClasses(Map<Class<?>, Object> args) {
		if (args.size() == 0) {
			return null;
		}
		List<Class<?>> classes = new LinkedList<Class<?>>();
		
		for (Class<?> clazz : args.keySet()) {
			classes.add(clazz);
		}
		
		return classes.toArray(new Class<?>[args.size()]);
	}
	
	private Object[] getParametersValues(Map<Class<?>, Object> args) {
		if (args.size() == 0) {
			return null;
		}
		
		List<Object> objects = new LinkedList<Object>();
		
		for (Class<?> clazz : args.keySet()) {
			objects.add(args.get(clazz));
		}
		
		return objects.toArray(new Object[args.size()]);
	}
	
	private String getFilter(Map<String, String> properties) {	
		if (properties.isEmpty()) {
			return genRemoteTrue();
		}
		
		String filter = "(&" + genRemoteTrue();
		
		for (String property : properties.keySet()) {
			filter += " (" + property + "=" + properties.get(property) + ")";
		}
		
		filter += ")";
		
		return filter;
	}
	
	private String genRemoteTrue() {
		return "(" + RemoteInvoker.REMOTE_PROPERTY_KEY + "=" + RemoteInvoker.REMOTE_PROPERTY_VALUE_TRUE + ")";
	}
}
